package in.co.codedoc.tomcat;

import in.co.codedoc.encode.URIEncoding;
import in.co.codedoc.framework.ServiceInvoker;
import in.co.codedoc.ioc.IOCContainer;
import in.co.codedoc.json.JSONArrayValue;
import in.co.codedoc.json.JSONObjectValue;
import in.co.codedoc.json.JSONStringValue;
import in.co.codedoc.json.JSONUtil;
import in.co.codedoc.json.JSONValue;
import in.co.codedoc.json.JSONXMLUtil;
import in.co.codedoc.rpc.RPCUtil;

import java.io.IOException;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
public class RPCServlet
	extends InvokeableServletBase
{
	@Override
	protected org.apache.log4j.Logger GetLogger()
	{
		return Logger.logger;
	}
	
	private static enum PayloadFormat
	{
		XML,
		JSON
	}
	
	public void serviceImpl(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException
	{
		JSONValue retval = JSONUtil.emptyObject;
		String payloadFormatName = request.getParameter("payloadFormat");
		PayloadFormat format 
			= ((payloadFormatName != null) && (payloadFormatName.equalsIgnoreCase("xml")))
				? PayloadFormat.XML : PayloadFormat.JSON;
		
		try
		{
			Pattern urlPattern = Pattern.compile("^.*/rpc/?([^/]*)");
			Matcher urlMatcher = urlPattern.matcher(request.getRequestURI());
			
			String encodedServiceName = null;
			
			if(urlMatcher.find())
			{
				encodedServiceName = urlMatcher.group(1);
			}
			
			if((encodedServiceName == null) || encodedServiceName.equals(""))
			{
				encodedServiceName = request.getParameter("service");
			}
			
			if((encodedServiceName != null) && !encodedServiceName.equals(""))
			{
				String serviceName = URIEncoding.Decode(encodedServiceName);
				
				JSONValue arguments
					= (request.getParameter("arguments") != null)
						?
							(
								format == PayloadFormat.XML
								? JSONXMLUtil.Decode(URIEncoding.Decode(request.getParameter("arguments")))
								: JSONValue.Decode(URIEncoding.Decode(request.getParameter("arguments")))
							)
						: MakeArguments(request);
				
				if(serviceName == null)
				{
					retval
						= JSONUtil.Encode
							(
								new ErrorResponse("Indicate the service to invoke using the 'service' paramemter."),
								"in.co.codedoc.tomcat.ErrorResponse"
							);
				}
				else if(arguments == null)
				{
					retval
						= JSONUtil.Encode
							(
								new ErrorResponse("Indicate the arguments to pass to the service using the 'arguments' paramemter."),
								"in.co.codedoc.tomcat.ErrorResponse"
							);
				}
				else if(!(arguments instanceof JSONObjectValue))
				{
					retval
						= JSONUtil.Encode
							(
								new ErrorResponse("The arguments passed to the service using the 'arguments' paramemter should be an encoded json object."),
								"in.co.codedoc.tomcat.ErrorResponse"
							);
				}
				else
				{
					JSONValue temp = RPCUtil.ExecuteService(serviceName,arguments);
					if(temp != null)
					{
						retval = temp;
					}
				}
			}
			else if(request.getParameter("invocations") != null)
			{
				JSONValue tmp
					= JSONValue.Decode(URIEncoding.Decode(request.getParameter("invocations")));
				
				if(!(tmp instanceof JSONArrayValue))
				{
					throw new RuntimeException("Invocations should be an array.");					
				}
				
				JSONArrayValue invocations  = (JSONArrayValue)tmp;
				
				retval = new JSONArrayValue();
				
				invocations.ForEach
				(
					new JSONArrayValue.ForEach<JSONArrayValue>()
					{
						public JSONArrayValue Do(int index,JSONValue entry,JSONArrayValue retval)
						{
							if(!(entry instanceof JSONObjectValue))
							{
								((JSONArrayValue)retval).Add
								(
									JSONUtil.Encode
									(
										new ErrorResponse("Invocation '" + entry.Encode() + "' should be an object"),
										"in.co.codedoc.tomcat.ErrorResponse"
									)
								);
							}
							else
							{
								JSONObjectValue invocation = (JSONObjectValue)entry;
							
								if(!invocation.HasProperty("service"))
								{
									((JSONArrayValue)retval).Add
									(
										JSONUtil.Encode
										(
											new ErrorResponse("Invocation '" + entry.Encode() + "' should indicate the service to invoke using the 'service' key."),
											"in.co.codedoc.tomcat.ErrorResponse"
										)
									);
								}
								else if(!invocation.HasProperty("arguments"))
								{
									((JSONArrayValue)retval).Add
									(
										JSONUtil.Encode
										(
											new ErrorResponse("Invocation '" + entry.Encode() + "' should indicate the arguments using the 'arguments' key."),
											"in.co.codedoc.tomcat.ErrorResponse"
										)
									);
								}
								else if(!(invocation.GetProperty("arguments") instanceof JSONObjectValue))
								{
									((JSONArrayValue)retval).Add
									(
										JSONUtil.Encode
										(
											new ErrorResponse("Arguments passed should be an encoded JSON Object."),
											"in.co.codedoc.tomcat.ErrorResponse"
										)
									);
								}
								else
								{
									JSONValue temp
										= RPCUtil.ExecuteService
											(
												invocation.GetProperty("service").Encode(),
												invocation.GetProperty("arguments")
											);

									if(temp != null)
									{
										((JSONArrayValue) retval).Add(temp);
									}
								}
							}
							
							return retval;
						}
					},
					(JSONArrayValue)retval
				);
			}	
		}
		catch(Throwable th)
		{
			Logger.logger.error
			(
				"Exception while invoking service. Exception:" + th.getMessage(),
				th
			);
			
			retval
				= JSONUtil.Encode
					(
							new ErrorResponse("Failed to invoke service(s):" + th.getMessage()),
							"in.co.codedoc.tomcat.ErrorResponse"
					);
		}
		
		
		response.addHeader("Cache-Control", "no-cache");
		response.setContentType("text/json");
		
		ServiceInvoker si = IOCContainer.Lookup(ServiceInvoker.class);
		si.AddCookies(response);

		response.getWriter().write
		(
			format.equals(PayloadFormat.JSON)
			? URIEncoding.Encode(retval.Encode())
			: "<?xml version=\"1.0\"?>\n\n" + JSONXMLUtil.Encode(retval)
		);
		response.getWriter().flush();
	}

	@SuppressWarnings("unchecked")
	private JSONValue MakeArguments(HttpServletRequest request)
	{
		JSONObjectValue arguments = new JSONObjectValue();
		
		Enumeration<String> parameters = request.getParameterNames();
		
		while(parameters.hasMoreElements())
		{
			String parameter = parameters.nextElement();
			
			String[] values = request.getParameterValues(parameter);
			
			if(values.length == 1)
			{
				arguments.SetProperty(parameter, new JSONStringValue(URIEncoding.Decode(values[0])));
			}
			else
			{
				JSONArrayValue arrayValue = new JSONArrayValue();
				for(String value:values)
				{
					arrayValue.Add(new JSONStringValue(URIEncoding.Decode(value)));
				}
				arguments.SetProperty(parameter, arrayValue);
			}
		}
		
		return arguments;
	}	
}
